#region File Description
//-----------------------------------------------------------------------------
// BaseGame.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

#region Using directives
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using System;
using System.Collections.Generic;
using System.Threading;
using RacingGame.GameLogic;
using RacingGame.Helpers;
using RacingGame.Properties;
using RacingGame.Shaders;
using RacingGame.Sounds;
using Microsoft.Xna.Framework.GamerServices;
using RacingGame.GameScreens;
#endregion

namespace RacingGame.Graphics
{
	public partial class BaseGame : Microsoft.Xna.Framework.Game
	{
		#region Constants
		private static readonly Color BackgroundColor = Color.Black;
		private const float FieldOfView = (float)Math.PI / 2,
			NearPlane = 0.5f,
			FarPlane = 1750;

		public const float ViewableFieldOfView = FieldOfView / 1.125f;
		#endregion

		#region Variables

		public static PlatformID CurrentPlatform = Environment.OSVersion.Platform;
		public static GraphicsDeviceManager graphicsManager = null;
		protected static ContentManager content = null;
		protected static UIRenderer ui = null;
		protected static int width, height;
		private static float aspectRatio = 1.0f;
		private static string remWindowsTitle = "";

		public static string WindowsTitle
		{
			get
			{
				return remWindowsTitle;
			}
		}

		private static LineManager2D lineManager2D = null;
		private static LineManager3D lineManager3D = null;

		private static MeshRenderManager meshRenderManager =
			new MeshRenderManager();

		private static Matrix worldMatrix,
			viewMatrix,
			projectionMatrix;

		private static Vector3 lightDirection = new Vector3(0, 0, 1);

		public static Vector3 LightDirection
		{
			get
			{
				return lightDirection;
			}
			set
			{
				lightDirection = value;
				lightDirection.Normalize();
			}
		}

		private static float elapsedTimeThisFrameInMs = 0.001f, totalTimeMs = 0,
			lastFrameTotalTimeMs = 0;

		private static float startTimeThisSecond = 0;

		private static int
			frameCountThisSecond = 0,
			totalFrameCount = 0,
			fpsLastSecond = 60;

		public static bool EveryMillisecond(int checkMilliseconds)
		{
			return (int)(lastFrameTotalTimeMs / checkMilliseconds) !=
				(int)(totalTimeMs / checkMilliseconds);
		}

		private static GamerServicesComponent gamerServicesComponent = null;
		public static GamerServicesComponent GamerServicesComponent
		{
			get { return gamerServicesComponent; }
		}

		#endregion

		#region Properties
		#region Device
		static public GraphicsDevice Device
		{
			get
			{
				return graphicsManager.GraphicsDevice;
			}
		}

		static DepthFormat backBufferDepthFormat = DepthFormat.Depth32;
		public static DepthFormat BackBufferDepthFormat
		{
			get
			{
				return backBufferDepthFormat;
			}
		}

		private static bool alreadyCheckedGraphicsOptions = false;

		internal static void CheckOptionsAndPSVersion()
		{
			GraphicsDevice device = Device;

			if (device == null)
				throw new InvalidOperationException("Device is not created yet!");

			alreadyCheckedGraphicsOptions = true;

			usePostScreenShaders = GameSettings.Default.PostScreenEffects;
			allowShadowMapping = GameSettings.Default.ShadowMapping;
			highDetail = GameSettings.Default.HighDetail;
		}

		public static bool Fullscreen
		{
			get
			{
				return graphicsManager.IsFullScreen;
			}
		}

		private static bool highDetail = true;

		public static bool HighDetail
		{
			get
			{
				if (alreadyCheckedGraphicsOptions == false)
					CheckOptionsAndPSVersion();

				return highDetail;
			}
		}

		private static bool allowShadowMapping = true;

		public static bool AllowShadowMapping
		{
			get
			{
				if (alreadyCheckedGraphicsOptions == false)
					CheckOptionsAndPSVersion();

				return allowShadowMapping;
			}
		}

		private static bool usePostScreenShaders = true;

		public static bool UsePostScreenShaders
		{
			get
			{
				if (alreadyCheckedGraphicsOptions == false)
					CheckOptionsAndPSVersion();

				return usePostScreenShaders;
			}
		}

		private static bool mustApplyDeviceChanges = false;
		internal static void ApplyResolutionChange()
		{
			int resolutionWidth = GameSettings.Default == null ? 0 :
				GameSettings.Default.ResolutionWidth;
			int resolutionHeight = GameSettings.Default == null ? 0 :
				GameSettings.Default.ResolutionHeight;
			// Use current desktop resolution if autodetect is selected.
			if (resolutionWidth <= 0 ||
				resolutionHeight <= 0)
			{
				resolutionWidth =
					GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
				resolutionHeight =
					GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
			}

#if XBOX360
            // Xbox 360 graphics settings are fixed
            graphicsManager.IsFullScreen = true;
            graphicsManager.PreferredBackBufferWidth =
                GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
            graphicsManager.PreferredBackBufferHeight =
                GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
#else
			graphicsManager.PreferredBackBufferWidth = resolutionWidth;
			graphicsManager.PreferredBackBufferHeight = resolutionHeight;
			graphicsManager.IsFullScreen = GameSettings.Default.Fullscreen;

			mustApplyDeviceChanges = true;
#endif
		}
		#endregion

		#region Content manager

		public new static ContentManager Content
		{
			get
			{
				return content;
			}
		}
		#endregion

		#region UI

		public static UIRenderer UI
		{
			get
			{
				return ui;
			}
		}
		#endregion

		#region MeshRenderManager

		public static MeshRenderManager MeshRenderManager
		{
			get
			{
				return meshRenderManager;
			}
		}
		#endregion

		#region Resolution and stuff

		public static int Width
		{
			get
			{
				return width;
			}
		}

		public static int Height
		{
			get
			{
				return height;
			}
		}

		public static float AspectRatio
		{
			get
			{
				return aspectRatio;
			}
		}

		public static Rectangle ResolutionRect
		{
			get
			{
				return new Rectangle(0, 0, width, height);
			}
		}
		#endregion

		#region Calc rectangle helpers

		public static int XToRes(int xIn1024px)
		{
			return (int)Math.Round(xIn1024px * BaseGame.Width / 1024.0f);
		}


		public static int YToRes(int yIn640px)
		{
			return (int)Math.Round(yIn640px * BaseGame.Height / 640.0f);
		}

		public static int YToRes768(int yIn768px)
		{
			return (int)Math.Round(yIn768px * BaseGame.Height / 768.0f);
		}

		public static int XToRes1600(int xIn1600px)
		{
			return (int)Math.Round(xIn1600px * BaseGame.Width / 1600.0f);
		}

		public static int YToRes1200(int yIn1200px)
		{
			return (int)Math.Round(yIn1200px * BaseGame.Height / 1200.0f);
		}

		public static int XToRes1400(int xIn1400px)
		{
			return (int)Math.Round(xIn1400px * BaseGame.Width / 1400.0f);
		}

		public static int YToRes1050(int yIn1050px)
		{
			return (int)Math.Round(yIn1050px * BaseGame.Height / 1050.0f);
		}

		public static Rectangle CalcRectangle(
			int relX, int relY, int relWidth, int relHeight)
		{
			float widthFactor = width / 1024.0f;
			float heightFactor = height / 640.0f;
			return new Rectangle(
				(int)Math.Round(relX * widthFactor),
				(int)Math.Round(relY * heightFactor),
				(int)Math.Round(relWidth * widthFactor),
				(int)Math.Round(relHeight * heightFactor));
		}

		public static Rectangle CalcRectangleWithBounce(
			int relX, int relY, int relWidth, int relHeight, float bounceEffect)
		{
			float widthFactor = width / 1024.0f;
			float heightFactor = height / 640.0f;
			float middleX = (relX + relWidth / 2) * widthFactor;
			float middleY = (relY + relHeight / 2) * heightFactor;
			float retWidth = relWidth * widthFactor * bounceEffect;
			float retHeight = relHeight * heightFactor * bounceEffect;
			return new Rectangle(
				(int)Math.Round(middleX - retWidth / 2),
				(int)Math.Round(middleY - retHeight / 2),
				(int)Math.Round(retWidth),
				(int)Math.Round(retHeight));
		}

		public static Rectangle CalcRectangleKeep4To3(
			int relX, int relY, int relWidth, int relHeight)
		{
			float widthFactor = width / 1024.0f;
			float heightFactor = height / 768.0f;
			return new Rectangle(
				(int)Math.Round(relX * widthFactor),
				(int)Math.Round(relY * heightFactor),
				(int)Math.Round(relWidth * widthFactor),
				(int)Math.Round(relHeight * heightFactor));
		}

		public static Rectangle CalcRectangleKeep4To3(
			Rectangle gfxRect)
		{
			float widthFactor = width / 1024.0f;
			float heightFactor = height / 768.0f;
			return new Rectangle(
				(int)Math.Round(gfxRect.X * widthFactor),
				(int)Math.Round(gfxRect.Y * heightFactor),
				(int)Math.Round(gfxRect.Width * widthFactor),
				(int)Math.Round(gfxRect.Height * heightFactor));
		}

		public static Rectangle CalcRectangle1600(
			int relX, int relY, int relWidth, int relHeight)
		{
			float widthFactor = width / 1600.0f;

			float heightFactor = (height / 1200.0f);// / (aspectRatio / (16 / 9));
			return new Rectangle(
				(int)Math.Round(relX * widthFactor),
				(int)Math.Round(relY * heightFactor),
				(int)Math.Round(relWidth * widthFactor),
				(int)Math.Round(relHeight * heightFactor));
		}

		public static Rectangle CalcRectangle2000(
			int relX, int relY, int relWidth, int relHeight)
		{
			float widthFactor = width / 2000.0f;
			float heightFactor = (height / 1500.0f);
			return new Rectangle(
				(int)Math.Round(relX * widthFactor),
				(int)Math.Round(relY * heightFactor),
				(int)Math.Round(relWidth * widthFactor),
				(int)Math.Round(relHeight * heightFactor));
		}

		public static Rectangle CalcRectangleKeep4To3AlignBottom(
			int relX, int relY, int relWidth, int relHeight)
		{
			float widthFactor = width / 1024.0f;
			float heightFactor16To9 = height / 640.0f;
			float heightFactor4To3 = height / 768.0f;
			return new Rectangle(
				(int)(relX * widthFactor),
				(int)(relY * heightFactor16To9) -
				(int)Math.Round(relHeight * heightFactor4To3),
				(int)Math.Round(relWidth * widthFactor),
				(int)Math.Round(relHeight * heightFactor4To3));
		}

		public static Rectangle CalcRectangleKeep4To3AlignBottomRight(
			int relX, int relY, int relWidth, int relHeight)
		{
			float widthFactor = width / 1024.0f;
			float heightFactor16To9 = height / 640.0f;
			float heightFactor4To3 = height / 768.0f;
			return new Rectangle(
				(int)(relX * widthFactor) -
				(int)Math.Round(relWidth * widthFactor),
				(int)(relY * heightFactor16To9) -
				(int)Math.Round(relHeight * heightFactor4To3),
				(int)Math.Round(relWidth * widthFactor),
				(int)Math.Round(relHeight * heightFactor4To3));
		}

		public static Rectangle CalcRectangleCenteredWithGivenHeight(
			int relX, int relY, int relHeight, Rectangle gfxRect)
		{
			float widthFactor = width / 1024.0f;
			float heightFactor = height / 640.0f;
			int rectHeight = (int)Math.Round(relHeight * heightFactor);
			// Keep aspect ratio
			int rectWidth = (int)Math.Round(
				gfxRect.Width * rectHeight / (float)gfxRect.Height);
			return new Rectangle(
				Math.Max(0, (int)Math.Round(relX * widthFactor) - rectWidth / 2),
				Math.Max(0, (int)Math.Round(relY * heightFactor) - rectHeight / 2),
				rectWidth, rectHeight);
		}
		#endregion

		#region Frames per second
		public static int Fps
		{
			get
			{
				return fpsLastSecond;
			}
		}

		private static float fpsInterpolated = 100.0f;

		public static int TotalFrames
		{
			get
			{
				return totalFrameCount;
			}
		}
		#endregion

		#region Timing stuff
		public static float ElapsedTimeThisFrameInMilliseconds
		{
			get
			{
				return elapsedTimeThisFrameInMs;
			}
		}

		public static float TotalTime
		{
			get
			{
				return totalTimeMs / 1000.0f;
			}
		}

		public static float TotalTimeMilliseconds
		{
			get
			{
				return totalTimeMs;
			}
		}

		public static float MoveFactorPerSecond
		{
			get
			{
				return elapsedTimeThisFrameInMs / 1000.0f;
			}
		}
		#endregion

		#region Camera
		public static Matrix WorldMatrix
		{
			get
			{
				return worldMatrix;
			}
			set
			{
				worldMatrix = value;
				// Update worldViewProj here?
			}
		}

		public static Matrix ViewMatrix
		{
			get
			{
				return viewMatrix;
			}
			set
			{
				// Set view matrix, usually only done in ChaseCamera.Update!
				viewMatrix = value;

				// Update camera pos and rotation, used all over the game!
				invViewMatrix = Matrix.Invert(viewMatrix);
				camPos = invViewMatrix.Translation;
				cameraRotation = Vector3.TransformNormal(
					new Vector3(0, 0, 1), invViewMatrix);
			}
		}

		public static Matrix ProjectionMatrix
		{
			get
			{
				return projectionMatrix;
			}
			set
			{
				projectionMatrix = value;
				// Update worldViewProj here?
			}
		}

		private static Vector3 camPos;

		public static Vector3 CameraPos
		{
			get
			{
				return camPos;
			}
		}

		private static Vector3 cameraRotation = new Vector3(0, 0, 1);

		public static Vector3 CameraRotation
		{
			get
			{
				return cameraRotation;
			}
		}

		private static Matrix invViewMatrix;

		public static Matrix InverseViewMatrix
		{
			get
			{
				return invViewMatrix;//Matrix.Invert(ViewMatrix);
			}
		}

		public static Matrix ViewProjectionMatrix
		{
			get
			{
				return ViewMatrix * ProjectionMatrix;
			}
		}

		public static Matrix WorldViewProjectionMatrix
		{
			get
			{
				return WorldMatrix * ViewMatrix * ProjectionMatrix;
			}
		}
		#endregion

		#region Render states
		public static void SetAlphaBlendingEnabled(bool value)
		{
			if (value)
			{
				Device.RenderState.AlphaBlendEnable = true;
				Device.RenderState.SourceBlend = Blend.SourceAlpha;
				Device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
			}
			else
			{
				Device.RenderState.AlphaBlendEnable = false;
			}
		}

		public enum AlphaMode
		{
			DisableAlpha,
			Default,
			SourceAlphaOne,
			OneOne,
		}

		public static void SetCurrentAlphaMode(AlphaMode value)
		{
			switch (value)
			{
				case AlphaMode.DisableAlpha:
					Device.RenderState.SourceBlend = Blend.Zero;
					Device.RenderState.DestinationBlend = Blend.One;
					break;
				case AlphaMode.Default:
					Device.RenderState.SourceBlend = Blend.SourceAlpha;
					Device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
					break;
				case AlphaMode.SourceAlphaOne:
					Device.RenderState.SourceBlend = Blend.SourceAlpha;
					Device.RenderState.DestinationBlend = Blend.One;
					break;
				case AlphaMode.OneOne:
					Device.RenderState.SourceBlend = Blend.One;
					Device.RenderState.DestinationBlend = Blend.One;
					break;
			}
		}
		#endregion
		#endregion

		#region Constructor
		protected BaseGame(string setWindowsTitle)
		{
			gamerServicesComponent = new GamerServicesComponent(this);
			base.Components.Add(gamerServicesComponent);

			// Set graphics
			graphicsManager = new GraphicsDeviceManager(this);

			// Set minimum requirements
			graphicsManager.MinimumPixelShaderProfile = ShaderProfile.PS_2_0;
			graphicsManager.MinimumVertexShaderProfile = ShaderProfile.VS_2_0;

			ApplyResolutionChange();

			graphicsManager.PreparingDeviceSettings +=
				new EventHandler<PreparingDeviceSettingsEventArgs>(
					graphics_PrepareDevice);

#if DEBUG
			// Disable vertical retrace to get highest framerates possible for
			// testing performance.
			graphicsManager.SynchronizeWithVerticalRetrace = false;
#endif
			// Update as fast as possible, do not use fixed time steps.
			// The whole game is designed this way, if you remove this line
			// the car will not behave normal anymore!
			this.IsFixedTimeStep = false;

			// Init content manager
			BaseGame.content = base.Content;
			base.Content.RootDirectory = String.Empty;

			// Update windows title (used for unit testing)
			this.Window.Title = setWindowsTitle;
			remWindowsTitle = setWindowsTitle;

			Sound.Initialize();
		}

		protected BaseGame()
			: this("Game")
		{
		}

		void graphics_PrepareDevice(object sender, PreparingDeviceSettingsEventArgs e)
		{
			if (Environment.OSVersion.Platform != PlatformID.Win32NT)
			{
				PresentationParameters presentParams =
					e.GraphicsDeviceInformation.PresentationParameters;

				presentParams.RenderTargetUsage = RenderTargetUsage.PlatformContents;
				if (graphicsManager.PreferredBackBufferHeight == 720)
				{
					presentParams.MultiSampleType = MultiSampleType.FourSamples;
#if !DEBUG
                    presentParams.PresentationInterval = PresentInterval.One;
#endif
				}
				else
				{
					presentParams.MultiSampleType = MultiSampleType.TwoSamples;
#if !DEBUG
                    presentParams.PresentationInterval = PresentInterval.Two;
#endif
				}
			}
		}

		protected override void Initialize()
		{
#if !XBOX360
			// Add screenshot capturer. Note: Don't do this in constructor,
			// we need the correct window name for screenshots!
			this.Components.Add(new ScreenshotCapturer(this));
#endif

			base.Initialize();

			GameSettings.Initialize();
			ApplyResolutionChange();
			Sound.SetVolumes(GameSettings.Default.SoundVolume,
				GameSettings.Default.MusicVolume);

			//Init the static screens
			Highscores.Initialize();

			// Replaces static Constructors with simple inits.
			Log.Initialize();

			// Set depth format
			backBufferDepthFormat = graphicsManager.PreferredDepthStencilFormat;

			// Update resolution if it changes
			graphicsManager.DeviceReset += new EventHandler(graphics_DeviceReset);
			graphics_DeviceReset(null, EventArgs.Empty);

			WorldMatrix = Matrix.Identity;

			// ViewMatrix is updated in camera class
			ViewMatrix = Matrix.CreateLookAt(
				new Vector3(0, 0, 250), Vector3.Zero, Vector3.Up);

			// Projection matrix is set by DeviceReset

			// Init global manager classes, which will be used all over the place ^^
			lineManager2D = new LineManager2D();
			lineManager3D = new LineManager3D();
			ui = new UIRenderer();
		}

		void graphics_DeviceReset(object sender, EventArgs e)
		{
			// Update width and height
			width = Device.Viewport.Width;//Window.ClientBounds.Width;
			height = Device.Viewport.Height;//Window.ClientBounds.Height;
			aspectRatio = (float)width / (float)height;
			ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(
				FieldOfView, aspectRatio, NearPlane, FarPlane);

			// Re-Set device
			// Restore z buffer state
			BaseGame.Device.RenderState.DepthBufferEnable = true;
			BaseGame.Device.RenderState.DepthBufferWriteEnable = true;
			// Set u/v addressing back to wrap
			BaseGame.Device.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
			BaseGame.Device.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
			// Restore normal alpha blending
			BaseGame.SetCurrentAlphaMode(BaseGame.AlphaMode.Default);

			// Set 128 and greate alpha compare for Model.Render
			BaseGame.Device.RenderState.ReferenceAlpha = 128;
			BaseGame.Device.RenderState.AlphaFunction = CompareFunction.Greater;

			// Recreate all render-targets
			foreach (RenderToTexture renderToTexture in remRenderToTextures)
				renderToTexture.HandleDeviceReset();
		}
		#endregion

		#region Helper methods for 3d-calculations
		/// <summary>
		/// Epsilon (1/1000000) for comparing stuff which is nearly equal.
		/// </summary>
		public const float Epsilon = 0.000001f;

		public static Point Convert3DPointTo2D(Vector3 point)
		{
			Vector4 result4 = Vector4.Transform(point,
				ViewProjectionMatrix);

			if (result4.W == 0)
				result4.W = BaseGame.Epsilon;
			Vector3 result = new Vector3(
				result4.X / result4.W,
				result4.Y / result4.W,
				result4.Z / result4.W);

			// Output result from 3D to 2D
			return new Point(
				(int)Math.Round(+result.X * (width / 2)) + (width / 2),
				(int)Math.Round(-result.Y * (height / 2)) + (height / 2));
		}

		public static bool IsInFrontOfCamera(Vector3 point)
		{
			Vector4 result = Vector4.Transform(
				new Vector4(point.X, point.Y, point.Z, 1),
				ViewProjectionMatrix);

			// Is result in front?
			return result.Z > result.W - NearPlane;
		}

		public static bool IsVisible(Vector3 point, float checkOffset)
		{
			Vector4 result = Vector4.Transform(
				new Vector4(point.X, point.Y, point.Z, 1),
				ViewProjectionMatrix);

			// Point must be in front of camera, else just skip everything.
			if (result.Z > result.W - NearPlane)
			{
				Vector2 screenPoint = new Vector2(
					result.X / result.W, result.Y / result.W);

				// Change checkOffset depending on how depth we are into the scene
				// for very near objects (z < 5) pass all tests!
				// for very far objects (z >> 5) only pass if near to +- 1.0f
				float zDist = Math.Abs(result.Z);
				if (zDist < 5.0f)
					return true;
				checkOffset = 1.0f + (checkOffset / zDist);

				return
					screenPoint.X >= -checkOffset && screenPoint.X <= +checkOffset &&
					screenPoint.Y >= -checkOffset && screenPoint.Y <= +checkOffset;
			}

			// Point is not in front of camera, return false.
			return false;
		}
		#endregion

		#region Line helper methods
		public static void DrawLine(Point startPoint, Point endPoint, Color color)
		{
			lineManager2D.AddLine(startPoint, endPoint, color);
		}

		public static void DrawLine(Point startPoint, Point endPoint)
		{
			lineManager2D.AddLine(startPoint, endPoint, Color.White);
		}

		public static void DrawLine(Vector3 startPos, Vector3 endPos, Color color)
		{
			lineManager3D.AddLine(startPos, endPos, color);
		}

		public static void DrawLine(Vector3 startPos, Vector3 endPos,
			Color startColor, Color endColor)
		{
			lineManager3D.AddLine(startPos, startColor, endPos, endColor);
		}

		public static void DrawLine(Vector3 startPos, Vector3 endPos)
		{
			lineManager3D.AddLine(startPos, endPos, Color.White);
		}

		public static void FlushLineManager2D()
		{
			lineManager2D.Render();
		}

		public static void FlushLineManager3D()
		{
			lineManager3D.Render();
		}
		#endregion

		#region Update
		/// <summary>
		/// Update
		/// </summary>
		protected override void Update(GameTime gameTime)
		{
			base.Update(gameTime);

			// Update all input states
			Input.Update();

			lastFrameTotalTimeMs = totalTimeMs;
			elapsedTimeThisFrameInMs =
				(float)gameTime.ElapsedRealTime.TotalMilliseconds;
			totalTimeMs += elapsedTimeThisFrameInMs;

			// Make sure elapsedTimeThisFrameInMs is never 0
			if (elapsedTimeThisFrameInMs <= 0)
				elapsedTimeThisFrameInMs = 0.001f;

			// Increase frame counter for FramesPerSecond
			frameCountThisSecond++;
			totalFrameCount++;

			// One second elapsed?
			if (totalTimeMs - startTimeThisSecond > 1000.0f)
			{
				// Calc fps
				fpsLastSecond = (int)(frameCountThisSecond * 1000.0f /
					(totalTimeMs - startTimeThisSecond));

				// Reset startSecondTick and repaintCountSecond
				startTimeThisSecond = totalTimeMs;
				frameCountThisSecond = 0;

				fpsInterpolated =
					MathHelper.Lerp(fpsInterpolated, fpsLastSecond, 0.1f);

				// Check out if our framerate is running very low. Then we can improve
				// rendering by reducing the number of objects we draw.
				if (fpsInterpolated < 5)
					Model.MaxViewDistance = 50;
				else if (fpsInterpolated < 12)
					Model.MaxViewDistance = 70;
				else if (fpsInterpolated < 16)
					Model.MaxViewDistance = 90;
				else if (fpsInterpolated < 20)
					Model.MaxViewDistance = 120;
				else if (fpsInterpolated < 25)
					Model.MaxViewDistance = 150;
				else if (fpsInterpolated < 30 ||
					HighDetail == false)
					Model.MaxViewDistance = 175;
			}

			// Update sound and music
			Sound.Update();
		}
		#endregion

		#region On activated and on deactivated
		// Check if app is currently active
		static bool isAppActive = true;
		/// <summary>
		/// Is app active
		/// </summary>
		/// <returns>Bool</returns>
		public static bool IsAppActive
		{
			get
			{
				return isAppActive;
			}
		}

		protected override void OnActivated(object sender, EventArgs args)
		{
			base.OnActivated(sender, args);
			isAppActive = true;
		}

		protected override void OnDeactivated(object sender, EventArgs args)
		{
			base.OnDeactivated(sender, args);
			isAppActive = false;
		}
		#endregion

		#region Draw
#if !DEBUG
        int renderLoopErrorCount = 0;
#endif
		protected override void Draw(GameTime gameTime)
		{
			try
			{
				ClearBackground();

				Texture.additiveSprite.Begin(SpriteBlendMode.Additive);
				Texture.alphaSprite.Begin(SpriteBlendMode.AlphaBlend);
				Render();
				meshRenderManager.Render();
				lineManager3D.Render();
				UIRenderer.Render(lineManager2D);
				PostUIRender();

				if (RacingGameManager.InGame && RacingGameManager.Player.Victory)
				{
					Texture.alphaSprite.Begin(SpriteBlendMode.AlphaBlend);

					int rank = GameScreens.Highscores.GetRankFromCurrentTime(
						RacingGameManager.Player.LevelNum,
						(int)RacingGameManager.Player.BestTimeMilliseconds);

					BaseGame.UI.GetTrophyTexture(
						rank == 0 ? UIRenderer.TrophyType.Gold :
						rank == 1 ? UIRenderer.TrophyType.Silver :
						UIRenderer.TrophyType.Bronze).
						RenderOnScreen(new Rectangle(
						BaseGame.Width / 2 - BaseGame.Width / 8,
						BaseGame.Height / 2 - BaseGame.YToRes(10),
						BaseGame.Width / 4, BaseGame.Height * 2 / 5));

					Texture.alphaSprite.End();
				}

				ui.RenderTextsAndMouseCursor();
			}
#if !DEBUG
            catch (Exception ex)
            {
                Log.Write("Render loop error: " + ex.ToString());
                if (renderLoopErrorCount++ > 100)
                    throw;
            }
#endif
			finally
			{
				// Dummy block to prevent error in debug mode
			}

			base.Draw(gameTime);

			// Apply device changes
			if (mustApplyDeviceChanges)
			{
				graphicsManager.ApplyChanges();
				mustApplyDeviceChanges = false;
			}
		}
		#endregion

		#region Render
		public delegate void RenderHandler();

		protected virtual void Render()
		{

		}

		protected virtual void PostUIRender()
		{
		
		}

		public static void ClearBackground()
		{
			//unsure if it clears depth correctly: Device.Clear(BackgroundColor);
			Device.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,
				BackgroundColor, 1.0f, 0);
		}
		#endregion

		#region Set and reset render targets
		static RenderTarget2D remSceneRenderTarget = null;
		static RenderTarget2D lastSetRenderTarget = null;

		static List<RenderToTexture> remRenderToTextures =
			new List<RenderToTexture>();

		public static void AddRemRenderToTexture(RenderToTexture renderToTexture)
		{
			remRenderToTextures.Add(renderToTexture);
		}

		public static RenderTarget2D CurrentRenderTarget
		{
			get
			{
				return lastSetRenderTarget;
			}
		}

		internal static void SetRenderTarget(RenderTarget2D renderTarget,
			bool isSceneRenderTarget)
		{
			Device.SetRenderTarget(0, renderTarget);
			if (isSceneRenderTarget)
				remSceneRenderTarget = renderTarget;
			lastSetRenderTarget = renderTarget;
		}

		internal static void ResetRenderTarget(bool fullResetToBackBuffer)
		{
			if (remSceneRenderTarget == null ||
				fullResetToBackBuffer)
			{
				remSceneRenderTarget = null;
				lastSetRenderTarget = null;
				Device.SetRenderTarget(0, null);
			}
			else
			{
				Device.SetRenderTarget(0, remSceneRenderTarget);
				lastSetRenderTarget = remSceneRenderTarget;
			}
		}
		#endregion
	}
}
